Skip to content

fix(drivers): map emit keys onto the names the Nova payload reads#668

Merged
frahlg merged 1 commit into
masterfrom
normalize-telemetry-keys
Jul 25, 2026
Merged

fix(drivers): map emit keys onto the names the Nova payload reads#668
frahlg merged 1 commit into
masterfrom
normalize-telemetry-keys

Conversation

@frahlg

@frahlg frahlg commented Jul 25, 2026

Copy link
Copy Markdown
Member

Summary

nova.DerTelemetry unmarshals a driver's emit blob directly, so a key it does
not recognise is dropped without an error and the value never leaves the
gateway.

Catalog drivers emit import_wh, export_wh, charge_wh, discharge_wh,
lifetime_wh and hz. DerTelemetry reads total_import_wh,
total_export_wh, total_charge_wh, total_discharge_wh,
total_generation_wh and freq_hz.

None of those six has ever reached Nova from a catalog driver. Energy
counters and grid frequency, from every driver in srcfl/device-drivers.

Our own zap.lua works around it per field — reading.import_wh = reading.total_import_wh — which is why the gap went unnoticed: the drivers
written here happened to do the right thing.

What changed

normalizeTelemetryKeys rewrites known aliases onto the names the payload
reads, at the emit boundary, so every driver is fixed at once.

The canonical @srcful/data-models spellings map onto the same internal names,
so srcfl/device-drivers can convert its catalog to W, Hz, L1_V,
SoC_nom_fract and total_import_Wh without a further host change.

Three rules keep it safe:

  • an alias never overwrites a key the driver set under the target name;
  • the driver's original keys are kept, because the UI and API surface a reading
    verbatim and removing one would change what an operator sees;
  • a parse failure returns the input untouched — the envelope is already
    validated by this point, and normalisation must never lose a reading.

Tests

go/internal/drivers/telemetry_keys_test.go:

  • catalog spellings (hz, import_wh, export_wh) land on the Nova names;
  • battery and PV counters (charge_wh, discharge_wh, lifetime_wh) likewise;
  • canonical spellings (W, Hz, L1_V, total_import_Wh) land on the same
    names;
  • a driver that already sets the target name keeps its own value;
  • a payload carrying both names passes through byte-identical, so FTW's own
    drivers are unaffected;
  • malformed input is returned unchanged.

go test ./internal/drivers/ ./internal/nova/ ./internal/telemetry/ passes.

Follow-up

rated_wrated_power_w and the battery v/adc_v/dc_a mappings
are deliberately left out. Both need a look at what the drivers actually mean
by those fields before mapping them, and neither is an energy counter that
silently reads as zero.

🤖 Generated with Claude Code

nova.DerTelemetry unmarshals a driver's emit blob directly, so a key it does
not recognise is dropped without an error and the value never leaves the
gateway.

Catalog drivers emit import_wh, export_wh, charge_wh, discharge_wh,
lifetime_wh and hz. DerTelemetry reads total_import_wh, total_export_wh,
total_charge_wh, total_discharge_wh, total_generation_wh and freq_hz. None of
those six has ever reached Nova from a catalog driver. Our own zap.lua works
around it by emitting both names for every affected field, which is why the
gap went unnoticed.

Normalise at the emit boundary instead, so every driver is fixed at once.
The canonical @srcful/data-models spellings map onto the same internal names,
so srcfl/device-drivers can convert its catalog without a further host
change.

An alias never overwrites a key the driver set under the target name, and the
driver's original keys are kept because the UI and API surface a reading
verbatim. A parse failure returns the input untouched rather than losing a
reading.

Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 41cf5f2b10

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"export_wh": "total_export_wh",
"charge_wh": "total_charge_wh",
"discharge_wh": "total_discharge_wh",
"lifetime_wh": "total_generation_wh",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict the lifetime-energy alias to PV telemetry

When nova.schema_mode: unified is enabled, this unconditional alias mislabels EV charger energy: ctek.lua, ctek_v2.lua, and ctek_hybrid.lua emit lifetime_wh on type:"ev" readings, but the rewrite populates DerTelemetry.TotalGenerationWh, which is a PV-only field, and unified encoding serializes it verbatim. Those EV reports therefore advertise lifetime consumed energy as PV generation; inspect the reading type before applying this alias or introduce an EV-specific destination.

Useful? React with 👍 / 👎.

@frahlg
frahlg merged commit d39ace3 into master Jul 25, 2026
11 checks passed
@frahlg
frahlg deleted the normalize-telemetry-keys branch July 25, 2026 17:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant